💬 与编译器交流的艺术
⚠️ 警告:本文已被标记为「恶搞 13.9.2.1.1 · 编译器语言学」
以下内容将解读各种 “与编译器交流” 的方式——
inline[[likely]]constexpr[[nodiscard]]noexcept[[assume()]]……
这些不是“特性”,它们是 “你和编译器之间的对话”
如果你真的学会了本文,你会开始 “和编译器聊天”
然后你的代码会变成 “一段持续的对话记录”
💻 程序员与编译器的日常对话:
程序员:inline 这个函数。” (请求)
编译器: “你说了算?不,我说了算。我考虑一下。”
程序员:[[likely]] 这个分支会走。” (提示)
编译器: “哦?你确定?行,我信你一次。”
程序员:constexpr 在编译期算。” (命令)
编译器: “遵命……等等,你传了个运行时变量?我只能在编译期算。”
程序员:[[nodiscard]] 不要忽略返回值。” (警告)
编译器: “好的,我会 ‘提醒’ 他们。”
程序员:noexcept 不会抛异常。” (承诺)
编译器: “你确定?如果你骗我,后果自负。”
程序员:[[assume()]] 假设这个条件永远成立。” (信任投票)
编译器: “……你让我无条件相信你?好吧,我信了,但如果你错了,UB 是你的事。”
inline · 优化请求
[[likely]]/[[unlikely]] · 分支预测
constexpr/constinit · 编译期求值
[[nodiscard]] · 警告
[[maybe_unused]] · 沉默
[[fallthrough]] · 意图声明
volatile · 内存行为
noexcept · 异常契约
restrict · 别名承诺
[[assume()]] · C++23 信任投票

与编译器交流的艺术

inline、likely、constexpr、nodiscard、noexcept、assume……以及更多与编译器对话的方式
📅 2026 年 8 月 18 日 ⏱ 阅读耗时:约 12 分钟 # 整活 · # 编译器 · # 提示 · # 属性

在之前的三篇文章中,我们以“老古董”的身份,捍卫了模板元编程,反驳了“用 constexpr 就够了”的说法。 但批评者可能会说:“你只是用模板做编译期计算,那你对 ‘其他’ 现代特性怎么看?”

好问题!今天,我们就来 “全面” 解读 “与编译器交流的各种方式”。 从 C++ 最古老的 inline,到 C++23 最新的 [[assume()]]—— 这些不是“特性”,它们是 “你和编译器之间的对话”。每一种都是一种 “沟通方式”,一种 “请求”,一种 “命令”, 一种 “信任投票”

所以,当有人说“你是老古董”时,你可以告诉他:“我不仅知道这些新特性,我还知道它们 ‘怎么跟编译器聊天’。”

💬 交流宣言 — “我不是在写代码,
我是在 ‘和编译器聊天’
inline‘请求’
[[likely]]‘建议’
constexpr‘命令’
[[nodiscard]]‘警告’
noexcept‘承诺’
[[assume()]]‘无条件信任’
—— ‘这就是编译器的语言学’。”

一、优化提示:给编译器“递小纸条”

1.1 inline —— “请求式”交流

inline 是最古老的“编译器对话”。它是 “请求” 的语气: “亲爱的编译器,如果方便的话,请把这个函数内联一下,好吗?” 编译器通常会说: “我考虑一下。” 然后它做自己的决定。这是一种 “礼貌的交流”

            inline int add(int a, int b) { return a + b; }
            // 程序员:“请内联我。”
            // 编译器:“嗯,好,我内联了。哦不,我没内联。看我心情。”
        

1.2 [[likely]] / [[unlikely]] —— “预测式”交流

C++20 加入的 [[likely]][[unlikely]]“预测” 的语气: “我觉得这个分支大概率会走。” 编译器会利用这些信息做 “分支预测优化”。 这就像你跟朋友说:“我猜他会来。”——编译器信了你,然后安排座位(预取指令)。

            if (x > 0) [[likely]] {
            // 程序员:“大概率走这里。”
            } else [[unlikely]] {
            // 程序员:“不太会走这里。”
            }
            // 编译器:“好的,我按你说的优化了。但如果你错了,性能下降是你的事。”
        

1.3 __attribute__((hot)) / ((cold)) —— “专横式”交流

GCC/Clang 的 hotcold 扩展是 “专横” 的语气: “这个函数是热点函数,你给我好好优化!” 编译器不太喜欢这种语气,但大部分时候会照做。 这就像你说:“帮我做这个,马上。”——同事虽然不爽,但还是做了。

            __attribute__((hot)) void hot_function() { /* 频繁调用 */ }
            __attribute__((cold)) void cold_function() { /* 很少调用 */ }
            // 程序员:“这个函数很重要!”
            // 编译器:“知道了知道了,别吼。”
        

1.4 restrict —— “承诺式”交流

restrict(C99 中就有,C++ 中通过 __restrict 或编译器扩展)是 “承诺” 的语气: “我承诺,这个指针不会和其他指针别名。” 编译器基于这个承诺可以做激进优化。 如果你撒谎……“未定义行为” 会找上你。

            void add(int* restrict a, int* restrict b, int* restrict c) {
                *c = *a + *b;
            }
            // 程序员:“我发誓它们不会重叠。”
            // 编译器:“好,我信你,然后做优化。如果你骗我……呵呵。”
        

二、编译期求值提示:向编译器“下命令”

2.1 constexpr —— “命令式”交流

constexpr“命令” 的语气:“在编译期求值这个值!” 但编译器会反驳:“我尽量,但如果我做不到(传入了运行时变量),我就只能在运行时算。” 这就像你跟下属说“把这个做完”,下属说“我尽量,但如果资源不够,我就做不了”。

            constexpr int square(int n) { return n * n; }
            constexpr int x = square(5);   // ✅ 编译期算
            int y = square(get_input());   // ❌ 运行时算
            // 程序员:“编译期算!”
            // 编译器:“好的……哦,你传了运行时变量,我只能在运行时算。”
        

2.2 constinit —— “强制命令式”交流

C++20 的 constinit“强制命令” 的语气: “我不管,这个变量必须在编译期初始化!如果你做不到,就别编译了。” 这比 constexpr 更“强硬”——它只用于变量,且 “必须在编译期初始化”

            constinit int x = 10;   // ✅ 必须在编译期初始化
            constinit int y = get_random(); // ❌ 编译错误!不能在编译期确定
            // 程序员:“我说了算!”
            // 编译器:“好的好的,别生气。我照做。”
        

三、静态警告提示:给用户“说悄悄话”

3.1 [[nodiscard]] —— “劝告式”交流

[[nodiscard]] 告诉编译器:“这个返回值很重要,不要忽略它。” 然后编译器会对忽略返回值的调用产生警告。这就像你在产品包装上写: “请勿吞食”——用户可能会忽略,但你 “警告” 了。

            [[nodiscard]] int get_important_value() { return 0xFFFFFFFF; }

            get_important_value();   // ⚠️ 警告:忽略返回值
            // 编译器:“你确定不要这个值?我提醒你了哦。”
        

3.2 [[maybe_unused]] —— “沉默式”交流

[[maybe_unused]] 告诉编译器:“这个变量可能没用,但我保留它。” 编译器本来会警告“未使用的变量”,但加上这个属性后,编译器就 “闭嘴” 了。 这就像你跟人说:“这个我留着,虽然可能用不上。”

            [[maybe_unused]] int temp = 0xCCCCCCCC;   // 编译器不会警告“未使用”
            // 程序员:“我知道它没用,但我留着。”
            // 编译器:“好吧,我不说了。”
        

3.3 [[fallthrough]] —— “意图声明式”交流

switch 语句中,[[fallthrough]] 告诉编译器: “我是故意 case 穿透的,不是忘记写 break。” 于是编译器不会警告你。这就像你说:“我故意的。”

            switch (x) {
            case 1:
                do_something();
                [[fallthrough]];   // 故意的!
            case 2:
                do_something_else();
            break;
            }
            // 程序员:“我故意穿透的,别吵。”
            // 编译器:“好吧,我不警告了。”
        

四、内存行为提示:告诉编译器“这个内存很奇怪”

4.1 volatile —— “警示式”交流

volatile 告诉编译器:“这个变量可能被 ‘外部’ 修改。” 于是编译器不会优化它的读写。这就像你说:“这个房间可能有老鼠,进出小心一点。”

            volatile int flag = 0;
            while (!flag) { /* 等待外部中断修改 flag */ }
            // 程序员:“这个变量可能在程序外部被改。”
            // 编译器:“好的,我不会缓存它,每次都从内存读。”
        

五、异常契约提示:给编译器“签合同”

5.1 noexcept —— “合同式”交流

noexcept“合同” 的语气:“我保证这个函数不会抛出异常。” 编译器基于此可以做优化(比如不生成栈展开代码)。如果你撒谎(真的抛出了异常), 程序会调用 std::terminate——“合同违约” 的后果。

            noexcept void safe_function() { /* 不会抛异常 */ }
            // 程序员:“我保证不会抛异常。”
            // 编译器:“好,我信了。如果被骗了,std::terminate 会来找你。”
        

六、C++23 新特性:[[assume()]] —— “无条件信任式”交流

6.1 最极端的“编译器对话”

C++23 加入了 [[assume(expression)]]。这是 “无条件信任” 的语气: “我 ‘绝对’ 相信这个条件永远为真,编译器你拿去优化吧。” 这是最 “危险” 的交流方式——如果条件为假,程序进入 “未定义行为”。 这就像你说:“我绝对相信你会准时到。”——如果你错了,后果自负。

            void process(int* p) {
                [[assume(p != nullptr)]];   // 我绝对相信 p 不是空指针
                *p = 0xCCCCCCCC;   // 编译器会优化掉空指针检查
            }
            // 程序员:“我赌 p 不是 nullptr。”
            // 编译器:“好,我赌了。如果你输了,UB 是你的事。”
        
⚠️ 信任的代价[[assume()]] 是最“危险”的交流方式。
它让编译器 “无条件信任” 程序员,
如果程序员错了,程序会进入 “未定义行为”
这叫 “信任投票”——你投了信任票,输了就 “自己承担后果”

七、与编译器交流的“段位”体系


😌 写在最后(真诚版)

以上所有内容都是 “夸张” 的,但它反映了 “一个真实的现象”

但“老古董”的说法是不成立的。一个真正的“老古董”不是“不知道新特性”, 而是 “知道如何用这些特性”——并且知道 “它们不能替代模板元编程”

所以,下次有人说你“老古董”时,你可以回答:
“我不仅知道 constexpr,我还知道 constinit[[assume()]] 我只是选择在合适的时候用它们。”

—— 一个“老古董”,但知道所有“新把戏”